package imp

import (
	sqlc "bgs/db/sqlc"
	activityUserProcedureGrpc "bgs/grpc/gen/service/activity_user_procedure"
	commonActivityGrpc "bgs/grpc/gen/shared/common_activity"
	"bgs/grpc/gen/shared/message"
	activityService "bgs/service/activity"
	"bgs/service/model"
	userService "bgs/service/user"
	"bgs/util"
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"time"
)

/****************************************************************************************/
/*                               activity_user_procedure entity of helper               */
/****************************************************************************************/

func transformAwardInfoEntityToPb(o *model.ActivityUserAwardExtInfo) *message.AwardInfo {
	var desc string
	awardType := sqlc.EmAwardType(o.AwardType)
	switch awardType {
	case sqlc.EmAwardTypeBCoin:
		desc = fmt.Sprintf("星币*%d", o.AwardInfo.Coins)
	case sqlc.EmAwardTypeRealPrize:
		desc = util.JoinString(util.MapToString(util.ToGenericSlice(o.AwardInfo.RealPrizePtrs), func(v interface{}) string {
			val := v.(*model.RealPrizeProduct)
			return fmt.Sprintf("%s*1", val.ProductName)
		}), ",")
	}
	return &message.AwardInfo{
		Type:  o.AwardType,
		Desc:  desc,
		Prize: o.AwardInfo.Prize,
	}
}

func mapAwardInfoEntityToPb(list []*model.ActivityUserAwardExtInfo, f func(*model.ActivityUserAwardExtInfo) *message.AwardInfo) []*message.AwardInfo {
	r := make([]*message.AwardInfo, len(list))
	for i, item := range list {
		r[i] = f(item)
	}
	return r
}

func transformPrizeEntityToPb(o sqlc.ListNormalActivityProductWithProductInfosByActivityIDRow) *activityUserProcedureGrpc.PrizeEntity {
	return &activityUserProcedureGrpc.PrizeEntity{
		ProductId:   o.ProductID,
		Quantity:    o.Quantity,
		ProductName: o.ProductName,
		ProductPics: o.ProductPics,
	}
}

func mapPrizeEntityToPb(list []sqlc.ListNormalActivityProductWithProductInfosByActivityIDRow, f func(sqlc.ListNormalActivityProductWithProductInfosByActivityIDRow) *activityUserProcedureGrpc.PrizeEntity) []*activityUserProcedureGrpc.PrizeEntity {
	r := make([]*activityUserProcedureGrpc.PrizeEntity, len(list))
	for i, item := range list {
		r[i] = f(item)
	}
	return r
}

func transformCheckIn1DefEntityToPb(
	typeInfoPtr *model.ActivityTypeInfo,
	activityProductWithProductInfos []sqlc.ListNormalActivityProductWithProductInfosByActivityIDRow,
) func(sqlc.GetCheckIn1ActivityOfU2CForUserByBrandIDRow) *activityUserProcedureGrpc.CheckIn1DefEntity {
	return func(o sqlc.GetCheckIn1ActivityOfU2CForUserByBrandIDRow) *activityUserProcedureGrpc.CheckIn1DefEntity {
		return &activityUserProcedureGrpc.CheckIn1DefEntity{
			ActivityId: o.ID,
			BrandId:    o.BrandID,
			Name:       o.Name,
			Type:       o.Type,
			Intro:      string(o.Intro),
			Background: o.Background.String,
			Poster:     o.Poster.String,
			Tel:        o.Tel,
			BrandName:  o.BrandName,
			BrandPic:   o.BrandPic,
			Prizes:     mapPrizeEntityToPb(activityProductWithProductInfos, transformPrizeEntityToPb),
			TypeInfo: &commonActivityGrpc.CheckIn1Entity{
				AwardOnceCoins:     uint32(typeInfoPtr.CheckIn1Ptr.AwardOnceCoins),
				AwardReachMaxCoins: uint32(typeInfoPtr.CheckIn1Ptr.AwardReachMaxCoins),
				Continuous:         typeInfoPtr.CheckIn1Ptr.MaxContinuous,
			},
			IsAvailable: o.ProcessStatus == string(sqlc.EmActivityProcessStatusProcessing),
		}
	}
}

func transformCheckIn1ProcedureEntityToPb(
	maxContinuous uint32,
	bCoins uint32,
) func(*model.CheckIn1Procedure) *activityUserProcedureGrpc.CheckIn1ProcedureEntity {
	return func(ptr *model.CheckIn1Procedure) *activityUserProcedureGrpc.CheckIn1ProcedureEntity {

		today := util.GoDateToString(time.Now())
		yesterday := util.GoDateToString(time.Now().AddDate(0, 0, -1))
		isCheckInToday := ptr.LastCheckInDate == today
		var isContinuous bool
		if isCheckInToday {
			// 今天签到过,则连签判定是
			isContinuous = true
		} else {
			// 今天没签到过,则连签判定是
			isContinuous = ptr.LastCheckInDate == yesterday
		}
		return &activityUserProcedureGrpc.CheckIn1ProcedureEntity{
			IsCheckInToday:         isCheckInToday,
			IsContinuous:           isContinuous,
			ContinuousCheckInCount: util.UInt32IfThenElse(isContinuous, ptr.ContinuousCheckInCount % maxContinuous, ptr.ContinuousCheckInCount), // 签到按照maxContinuous循环,所以要取榆树
			BCoins:                 bCoins,
		}
	}
}

func transformCheckIn1StatEntityToPb(
	o sqlc.ActivityExposureStat,
) *activityUserProcedureGrpc.CheckIn1StatEntity {
	return &activityUserProcedureGrpc.CheckIn1StatEntity{
		LatestAccessed: sqlc.GoTimeToPBTimestamp(o.LatestAccessed),
		Pv:             o.Pv,
		Uv:             o.Uv,
		Iv:             o.Iv,
	}
}

func transformActivityNeedTypeInfoEntityToPb(
	activityProductWithProductInfos []sqlc.ListNormalActivityProductWithProductInfosByActivityIDRow,
) func(sqlc.GetActivityNeedTypeInfoForUserRow) *activityUserProcedureGrpc.ActivityNeedTypeInfoEntity {
	return func(o sqlc.GetActivityNeedTypeInfoForUserRow) *activityUserProcedureGrpc.ActivityNeedTypeInfoEntity {
		var timeRangeEntities []*message.TimeRange
		for _, tr := range o.TimeRanges {
			timeRangeEntities = append(timeRangeEntities, &message.TimeRange{
				BeginAt: tr.BeginAt,
				EndAt:   tr.EndAt,
			})
		}
		typeInfo := &model.ActivityTypeInfo{}
		json.Unmarshal(o.TypeInfo.RawMessage, typeInfo)
		return &activityUserProcedureGrpc.ActivityNeedTypeInfoEntity{
			ActivityId:    o.ID,
			BrandId:       o.BrandID,
			Name:          o.Name,
			Type:          o.Type,
			Intro:         string(o.Intro),
			ProcessStatus: o.ProcessStatus,
			Published:     sqlc.GoNTimeToPBTimestamp(o.Published),
			Begined:       sqlc.GoTimeToPBTimestamp(o.Begined),
			Ended:         sqlc.GoTimeToPBTimestamp(o.Ended),
			TimeRanges:    timeRangeEntities,
			Background:    o.Background.String,
			Poster:        o.Poster.String,
			Tel:           o.Tel,
			BrandName:     o.BrandName,
			BrandPic:      o.BrandPic,
			Prizes:        mapPrizeEntityToPb(activityProductWithProductInfos, transformPrizeEntityToPb),
			TypeInfo: &activityUserProcedureGrpc.ActivityNeedTypeInfoEntity_Lottery_1{
				Lottery_1: &commonActivityGrpc.Lottery1Entity{
					UIType:     typeInfo.Lottery1Ptr.UIType,
					WinRate:    uint32(typeInfo.Lottery1Ptr.WinRate),
					PrizeRates: typeInfo.Lottery1Ptr.PrizeRates,
				},
			},
		}
	}
}

func transformLottery1ProcedureEntityToPb(ptr *model.Lottery1Procedure) *activityUserProcedureGrpc.Lottery1ProcedureEntity {
	return &activityUserProcedureGrpc.Lottery1ProcedureEntity{
		AvailableCount: ptr.AvailableCount,
		DrawedCount:    ptr.DrawedCount,
	}
}

/****************************************************************************************/
/*                               activity_user_procedure entity of cmd                  */
/****************************************************************************************/

// DoCheckIn1 Do签到1
func (s *ActivityAPIService) DoCheckIn1(ctx context.Context, req *activityUserProcedureGrpc.DoCheckIn1Request) (res *activityUserProcedureGrpc.DoCheckIn1Response, err error) {
	slog := s.NewContextLogger(ctx)
	slog.Infof("activity_user_procedureImp->DoCheckIn1:%v", req)
	res = &activityUserProcedureGrpc.DoCheckIn1Response{
		OpRet: &message.OperationResult{
			Success: true,
			Code:    0,
		},
	}

	activityID := req.ActivityId
	userID := req.UserId
	userOpenID := req.UserOpenId

	var userAwards []*model.ActivityUserAwardExtInfo
	err = s.dao.NewTxRunner().SqlcExecTx(ctx, func(q *sqlc.Queries) (err error) {
		activity, err := q.GetActivity(ctx, activityID)
		if err != nil {
			return
		}
		if activity.Type != string(sqlc.EmActivityTypeCheckIn1) {
			err = fmt.Errorf("类型不是check_in__1")
			return
		}
		activityPtr := &activity
		err = s.activityService.CheckActivityBeforeUserInteracted(activityPtr)
		if err != nil {
			return
		}
		slog.Infof("a activityService.CheckActivityBeforeUserInteracted ok")

		activityUserProcedureLocked, isCreated, err := s.activityService.EnsureActivityUserProcedure(ctx, q, true, activityID, userID)
		if err != nil {
			return
		}
		slog.Infof("b 签到 activityService get user_procedures -确保获取锁定的活动用户过程记录(%d)(%d) ok", activityID, userID)

		if isCreated {
			// 更新用户统计信息
			err = s.userService.UpdateStatSinceFirstActivityUserInteracted(ctx, q, userID)
			if err != nil {
				return
			}

			slog.Infof("c1 userService.UpdateStatSinceActivityUserProcedureCreated ok")

			err = s.activityService.UpdateActivityExposureStatSinceInteractedByC(ctx, q, activityID)
			if err != nil {
				return
			}
			slog.Infof("c2 activityService.UpdateActivityExposureStatSinceInteractedByC ok")
		}

		userAwards, err = s.activityService.UpdateActivityUserProcedureSinceCheckIn1(ctx, q, activityService.UpdateActivityUserProcedureSinceActivityUserInterativeParam{
			ActivityPtr:                    activityPtr,
			ActivityUserProcedureLockedPtr: &activityUserProcedureLocked,
		})
		if err != nil {
			return
		}
		slog.Infof("d activityService.UpdateActivityUserProcedureSinceCheckIn1 ok")

		userAwardsOfBCoin := util.Filter(util.ToGenericSlice(userAwards), func(v interface{}) bool {
			val := v.(*model.ActivityUserAwardExtInfo)
			return val.AwardType == string(sqlc.EmAwardTypeBCoin) && val.AwardInfo.Coins > 0
		})

		for _, i := range userAwardsOfBCoin {
			err = s.userService.LoggingBCoinForActivityAward(ctx, q, userService.LoggingBCoinForActivityAwardParam{
				UserID:                      userID,
				ActivityPtr:                 activityPtr,
				ActivityUserAwardExtInfoPtr: i.(*model.ActivityUserAwardExtInfo),
			})
			if err != nil {
				return
			}
		}

		if req.AuthorizeNextRemind {
			err = s.activityService.UpsertActivityUserNotification(ctx, q, activityService.UpsertActivityUserNotificationParam{
				ActivityPtr: activityPtr,
				UserID:      userID,
				UserOpenID:  userOpenID,
				Type:        sqlc.EmActivityNotificationTypeRemind,
			})
			if err != nil {
				return
			}
			slog.Infof("e activityService.UpsertActivityUserNotification ok")
		}

		return
	})
	if err != nil {
		res.OpRet = &message.OperationResult{
			Success: false,
			Code:    400,
			Msg:     err.Error(),
		}
		return
	}

	res.AwardInfos = mapAwardInfoEntityToPb(userAwards, transformAwardInfoEntityToPb)
	return
}

// DoLottery1 Do抽奖1
func (s *ActivityAPIService) DoLottery1(ctx context.Context, req *activityUserProcedureGrpc.DoLottery1Request) (res *activityUserProcedureGrpc.DoLottery1Response, err error) {
	slog := s.NewContextLogger(ctx)
	slog.Infof("activity_user_procedureImp->DoLottery1:%v", req)
	res = &activityUserProcedureGrpc.DoLottery1Response{
		OpRet: &message.OperationResult{
			Success: true,
			Code:    0,
		},
	}

	activityID := req.ActivityId
	userID := req.UserId

	var userAwards []*model.ActivityUserAwardExtInfo
	var lottery1PrecedurePtr *model.Lottery1Procedure

	err = s.dao.NewTxRunner().SqlcExecTx(ctx, func(q *sqlc.Queries) (err error) {
		activity, err := q.GetActivity(ctx, activityID)
		if err != nil {
			return
		}
		if activity.Type != string(sqlc.EmActivityTypeLottery1) {
			err = fmt.Errorf("类型不是lottery__1")
			return
		}
		activityPtr := &activity
		err = s.activityService.CheckActivityBeforeUserInteracted(activityPtr)
		if err != nil {
			return
		}
		slog.Infof("a activityService.CheckActivityBeforeUserInteracted ok")

		activityUserProcedureLocked, isCreated, err := s.activityService.EnsureActivityUserProcedure(ctx, q, true, activityID, userID)
		if err != nil {
			return
		}
		slog.Infof("b 抽奖 activityService get user_procedures -确保获取锁定的活动用户过程记录(%d)(%d) ok", activityID, userID)

		if isCreated {
			// 更新用户统计信息
			err = s.userService.UpdateStatSinceFirstActivityUserInteracted(ctx, q, userID)
			if err != nil {
				return
			}
			slog.Infof("c1 userService.UpdateStatSinceActivityUserProcedureCreated ok")

			err = s.activityService.UpdateActivityExposureStatSinceInteractedByC(ctx, q, activityID)
			if err != nil {
				return
			}
			slog.Infof("c2 activityService.UpdateActivityExposureStatSinceInteractedByC ok")
		}

		userAwards, lottery1PrecedurePtr, err = s.activityService.UpdateActivityUserProcedureSinceLottery1(ctx, q, activityService.UpdateActivityUserProcedureSinceActivityUserInterativeParam{
			ActivityPtr:                    activityPtr,
			ActivityUserProcedureLockedPtr: &activityUserProcedureLocked,
		})
		if err != nil {
			return
		}

		return
	})
	if err != nil {
		res.OpRet = &message.OperationResult{
			Success: false,
			Code:    400,
			Msg:     err.Error(),
		}
		return
	}

	res.AwardInfos = mapAwardInfoEntityToPb(userAwards, transformAwardInfoEntityToPb)
	res.Procedure = transformLottery1ProcedureEntityToPb(lottery1PrecedurePtr)
	return
}

/****************************************************************************************/
/*                               activity_user_procedure entity of query                */
/****************************************************************************************/

// GetCheckIn1 C端用户获取签到1活动的定义及过程
func (s *ActivityAPIService) GetCheckIn1(ctx context.Context, req *activityUserProcedureGrpc.GetCheckIn1Request) (res *activityUserProcedureGrpc.GetCheckIn1Response, err error) {
	slog := s.NewContextLogger(ctx)
	slog.Infof("activity_user_procedureImp->GetCheckIn1:%v", req)
	res = &activityUserProcedureGrpc.GetCheckIn1Response{
		OpRet: &message.OperationResult{
			Success: true,
			Code:    0,
		},
	}

	q := s.dao.Q
	brandID := req.BrandId
	userID := req.UserId

	checkIn1Def, err := q.GetCheckIn1ActivityOfU2CForUserByBrandID(ctx, brandID)
	if err != nil {
		res.OpRet = &message.OperationResult{
			Success: false,
			Code:    util.Int32IfThenElse(sql.ErrNoRows == err, 404, 400),
			Msg:     err.Error(),
		}
		return
	}

	activityID := checkIn1Def.ID

	normalActivityProductWithProductInfos, err := s.activityService.FetchNormalProducts(ctx, q, activityID)
	if err != nil {
		res.OpRet = &message.OperationResult{
			Success: false,
			Code:    400,
			Msg:     err.Error(),
		}
		return
	}

	var checkIn1PrecedurePtr *model.CheckIn1Procedure
	userProcedure, err := q.GetActivityUserProcedureByActivityIDAndUserID(ctx, sqlc.GetActivityUserProcedureByActivityIDAndUserIDParams{
		ActivityID: activityID,
		UserID:     userID,
	})
	if err != nil {
		if sql.ErrNoRows != err {
			res.OpRet = &message.OperationResult{
				Success: false,
				Code:    util.Int32IfThenElse(sql.ErrNoRows == err, 404, 400),
				Msg:     err.Error(),
			}
			return
		}
		checkIn1PrecedurePtr = &model.CheckIn1Procedure{}
	} else {
		procedureExtInfoPtr := &model.ActivityUserProcedureExtInfo{}
		json.Unmarshal(userProcedure.ExtInfo.RawMessage, procedureExtInfoPtr)
		checkIn1PrecedurePtr = &procedureExtInfoPtr.CheckIn1
	}

	aggrToInOut, err := q.AggrUserBCoinLogsToInOutByBrandIDAndUserID(ctx, sqlc.AggrUserBCoinLogsToInOutByBrandIDAndUserIDParams{
		BrandID: checkIn1Def.BrandID,
		UserID:  userID,
	})
	if err != nil {
		res.OpRet = &message.OperationResult{
			Success: false,
			Code:    400,
			Msg:     err.Error(),
		}
		return
	}

	typeInfoPtr := &model.ActivityTypeInfo{}
	json.Unmarshal(checkIn1Def.TypeInfo.RawMessage, typeInfoPtr)
	maxContinuous := typeInfoPtr.CheckIn1Ptr.MaxContinuous

	var activityExposureStat sqlc.ActivityExposureStat
	activityExposureStat, sqlErr := q.GetActivityExposureStatByActivityID(ctx, activityID)
	if sqlErr != nil {
		if sql.ErrNoRows != sqlErr {
			err = sqlErr
			return
		}
	}

	res.Def = transformCheckIn1DefEntityToPb(typeInfoPtr, normalActivityProductWithProductInfos)(checkIn1Def)
	res.Procedure = transformCheckIn1ProcedureEntityToPb(maxContinuous, uint32(aggrToInOut.Income))(checkIn1PrecedurePtr)
	res.Stat = transformCheckIn1StatEntityToPb(activityExposureStat)
	return
}

// GetLottery1 C端用户获取抽奖1活动的定义及过程
func (s *ActivityAPIService) GetLottery1(ctx context.Context, req *activityUserProcedureGrpc.GetLottery1Request) (res *activityUserProcedureGrpc.GetLottery1Response, err error) {
	slog := s.NewContextLogger(ctx)
	slog.Infof("activity_user_procedureImp->GetLottery1:%v", req)
	res = &activityUserProcedureGrpc.GetLottery1Response{
		OpRet: &message.OperationResult{
			Success: true,
			Code:    0,
		},
	}

	q := s.dao.Q
	activityID := req.ActivityId
	userID := req.UserId

	activityNeedTypeInfoDef, err := q.GetActivityNeedTypeInfoForUser(ctx, activityID)
	if err != nil {
		res.OpRet = &message.OperationResult{
			Success: false,
			Code:    util.Int32IfThenElse(sql.ErrNoRows == err, 404, 400),
			Msg:     err.Error(),
		}
		return
	}

	normalActivityProductWithProductInfos, err := s.activityService.FetchNormalProducts(ctx, q, activityID)
	if err != nil {
		res.OpRet = &message.OperationResult{
			Success: false,
			Code:    400,
			Msg:     err.Error(),
		}
		return
	}

	var lottery1PrecedurePtr *model.Lottery1Procedure
	activityUserProcedure, _, err := s.activityService.EnsureActivityUserProcedure(ctx, q, false, activityID, userID)
	if err != nil {
		res.OpRet = &message.OperationResult{
			Success: false,
			Code:    400,
			Msg:     err.Error(),
		}
		return
	}
	procedureExtInfoPtr := &model.ActivityUserProcedureExtInfo{}
	json.Unmarshal(activityUserProcedure.ExtInfo.RawMessage, procedureExtInfoPtr)
	lottery1PrecedurePtr = &procedureExtInfoPtr.Lottery1

	res.Def = transformActivityNeedTypeInfoEntityToPb(normalActivityProductWithProductInfos)(activityNeedTypeInfoDef)
	res.Procedure = transformLottery1ProcedureEntityToPb(lottery1PrecedurePtr)
	return
}
